///\todo upgrade

//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_MAT_CEXPR_H_
#define _BUOLA_MAT_CEXPR_H_

#include <buola/mat/detail/forward.h>
#include <buola/mat/detail/traits.h>
#include <buola/mat/detail/cexpressionop.h>
#include <buola/mat/detail/cnameop.h>
#include <buola/mat/detail/assign.h>

#include <buola/io.h>

namespace buola { namespace mat {

DECLARE_NAME(Upper)
DECLARE_NAME(Lower)
DECLARE_NAME(ConstructSub)
    
//base class for all matrix expressions
//it just implements the common interface so that it can be used in objects of type CExpr

template<typename tDerived>
class CExpr
{
    typedef detail::MMatrixTraits<tDerived> TTraits;
    typedef typename TTraits::TScalar TScalar;
    
public:
    tDerived &Derived()             {   return static_cast<tDerived&>(*this);           }
    const tDerived &Derived() const {   return static_cast<const tDerived&>(*this);     }

    detail::CStarOp<const tDerived&> operator*() const      {   return {Derived()};                                 }
    detail::CStarOp<tDerived&> operator*()                  {   return {Derived()};                                 }

    TScalar operator()(size_t r,size_t c) const             {   return Derived().template At<TScalar>(r,c);         }
    TScalar &operator()(size_t r,size_t c)                  {   return Derived().template At<TScalar>(r,c);         }

    template<bool b=(TTraits::cRows==1||TTraits::cCols==1),typename std::enable_if<b,int>::type=0>
    TScalar &operator()(size_t i)                           {   return Derived().template At<TScalar>(i);           }
    template<bool b=(TTraits::cRows==1||TTraits::cCols==1),typename std::enable_if<b,int>::type=0>
    TScalar operator()(size_t i) const                      {   return Derived().template At<TScalar>(i);           }

    TScalar &operator[](size_t i)                           {   return Derived().template At<TScalar>(i);           }
    TScalar operator[](size_t i) const                      {   return Derived().template At<TScalar>(i);           }

    TScalar &x()                                            {   return Derived().template At<TScalar>(0);           }
    TScalar x() const                                       {   return Derived().template At<TScalar>(0);           }
    TScalar &y()                                            {   return Derived().template At<TScalar>(1);           }
    TScalar y() const                                       {   return Derived().template At<TScalar>(1);           }
    TScalar &z()                                            {   return Derived().template At<TScalar>(2);           }
    TScalar z() const                                       {   return Derived().template At<TScalar>(2);           }
    TScalar &w()                                            {   return Derived().template At<TScalar>(3);           }
    TScalar w() const                                       {   return Derived().template At<TScalar>(3);           }
    
    template<bool b=(TTraits::cRows==1&&TTraits::cCols==1),typename std::enable_if<b,int>::type=0>
    operator TScalar() const
    {
        return detail::element_accessible<detail::ACCESS_ONCE>(Derived())(0,0);
    }
    
    detail::CNameOp<const tDerived&> operator()(const std::wstring &pName) const
    {
        return {Derived(),pName};
    }

    detail::CNameOp<const tDerived&> operator()(const std::string &pName) const
    {
        return {Derived(),utf32(pName)};
    }

    detail::CTranspose<detail::OConstNestType<tDerived>> T() const;
    detail::CTranspose<detail::ONonConstNestType<tDerived>> T();

    detail::CInverse<detail::OConstNestType<tDerived>> I() const;
    
    detail::CBlock<detail::OConstNestType<tDerived>,1,TTraits::cCols,true,false> operator()(size_t r,NAll) const;
    detail::CBlock<detail::ONonConstNestType<tDerived>,1,TTraits::cCols,true,false> operator()(size_t r,NAll);
    detail::CBlock<detail::OConstNestType<tDerived>,TTraits::cRows,1,false,true> operator()(NAll,size_t c) const;
    detail::CBlock<detail::ONonConstNestType<tDerived>,TTraits::cRows,1,false,true> operator()(NAll,size_t c);
    detail::CBlock<detail::OConstNestType<tDerived>,TTraits::cRows,cDynamic,false,true> operator()(NAll,size_t pFC,size_t pNC) const;
    detail::CBlock<detail::ONonConstNestType<tDerived>,TTraits::cRows,cDynamic,false,true> operator()(NAll,size_t pFC,size_t pNC);
    detail::CBlock<detail::OConstNestType<tDerived>,cDynamic,TTraits::cCols,false,true> operator()(size_t pFR,size_t pNR,NAll) const;
    detail::CBlock<detail::ONonConstNestType<tDerived>,cDynamic,TTraits::cCols,false,true> operator()(size_t pFR,size_t pNR,NAll);

    template<typename tSelection,OEnableIf<detail::QBooleanVector<tSelection>> =0>
    detail::CColSelection<const tDerived&,std::vector<size_t>> operator()(NAll,const CExpr<tSelection> &pSelection);
    template<typename tSelection,OEnableIf<detail::QBooleanVector<tSelection>> =0>
    detail::CRowSelection<const tDerived&,std::vector<size_t>> operator()(const CExpr<tSelection> &pSelection,NAll);
    
    detail::CColSelection<const tDerived&,std::vector<size_t>> operator()(NAll,const std::vector<size_t> &pSelection);
    detail::CRowSelection<const tDerived&,std::vector<size_t>> operator()(const std::vector<size_t> &pSelection,NAll);
    
    template<typename tPick,OEnableIf<detail::QBoolean<tPick>> =0>
    detail::CPickSelection<detail::OConstNestType<tDerived>,const tPick&> operator[](const CExpr<tPick> &pPick) const;
    template<typename tPick,OEnableIf<detail::QBoolean<tPick>> =0>
    detail::CPickSelection<detail::ONonConstNestType<tDerived>,const tPick&> operator[](const CExpr<tPick> &pPick);

    template<typename tRH,bool b=detail::QExprAssignable<tDerived>::value,typename std::enable_if<b,int>::type=0>
    const tDerived& operator=(const CExpr<tRH> &pRH) const
    {
        detail::assign<TScalar>(Derived(),pRH.Derived());
        return Derived();
    }
};

template<typename tDerived>
inline io::CTextWriter &operator<<(io::CTextWriter &pWriter,const CExpr<tDerived> &pExpr)
{
    const auto &lExpr=detail::element_accessible<detail::ACCESS_ONCE>(pExpr.Derived());
    
    for(int i=0;i<lExpr.Rows();i++)
    {
        for(int j=0;j<lExpr.Cols();j++)
        {
            pWriter << lExpr(i,j) << "\t";
        }
        pWriter << "\n";
    }
    return pWriter << "\n";
}

/*namespace mat*/ } /*namespace buola*/ }

#endif
